#include <zenox/home.hpp>
#include <zenox/unicode.hpp>
#include <zenox/fileutils.hpp>
#include <zenox/subroutine.hpp>
#include <zenox/subprocess.hpp>
#include <tbb/task_group.h>
#include <sstream>
#include <thread>
#include <chrono>
#include <regex>
#ifdef _WIN32
#include <windows.h>
#endif

namespace zenoui {

template <class Callback>
static void visit_directory(std::filesystem::path const &directory, Callback &&callback) {
    for (auto const &entry : std::filesystem::recursive_directory_iterator(directory)) {
        if (std::filesystem::is_regular_file(entry.path())) {
            callback(entry.path());
        }
    }
}

template <class Callback>
static void visit_directory(std::string const &directory, Callback &&callback) {
    for (auto const &entry : std::filesystem::recursive_directory_iterator(zenox::u8path(directory))) {
        if (std::filesystem::is_regular_file(entry.path())) {
            callback(zenox::pathu8(entry.path()));
        }
    }
}

template <class Callback>
static void visit_directory_relative(std::filesystem::path const &directory, Callback &&callback) {
    for (auto const &entry : std::filesystem::recursive_directory_iterator(directory)) {
        if (std::filesystem::is_regular_file(entry.path())) {
            callback(std::filesystem::relative(entry.path(), directory));
        }
    }
}

template <class Callback>
static void visit_directory_relative(std::string const &directory, Callback &&callback) {
    for (auto dir = zenox::u8path(directory); auto const &entry : std::filesystem::recursive_directory_iterator(dir)) {
        if (std::filesystem::is_regular_file(entry.path())) {
            callback(zenox::pathu8(std::filesystem::relative(entry.path(), dir)));
        }
    }
}

static std::string md2html(std::string md) {
    auto tmp = std::filesystem::temp_directory_path();
    //zenox::log_warn() << std::filesystem::temp_directory_path().string();
    //zenox::log_warn() << std::filesystem::temp_directory_path().generic_string();
    //zenox::log_warn() << std::string_view((char const *)std::filesystem::temp_directory_path().native().c_str(), std::filesystem::temp_directory_path().native().size());
    //exit(1);
    static unsigned int uniq_id = 0;
    uniq_id++;
    auto mdpath = zenox::pathu8(tmp / zenox::u8path("_zenopage-" + std::to_string(uniq_id) + ".md"));
    auto htmlpath = zenox::pathu8(tmp / zenox::u8path("_zenopage-" + std::to_string(uniq_id) + ".html"));
    zenox::file_put_content(mdpath, md);
    zenox::subprocess(zenox::zeno_executable_dir_relative("md2html")).argpath(mdpath).arg("-o").argpath(htmlpath).run().wait().ok();
/*     std::ostringstream oss; */
/* #ifndef _WIN32 */
/*     oss << std::quoted(zenox::u8path(zenox::zeno_executable_dir_relative("md2html")).string(), '\'', '\\'); */
/*     oss << ' '; */
/*     oss << std::quoted(zenox::u8path(mdpath).string(), '\'', '\\'); */
/*     oss << " -o "; */
/*     oss << std::quoted(zenox::u8path(htmlpath).string(), '\'', '\\'); */
/* #else */
/*     auto tmp1 = zenox::u8path(zenox::zeno_executable_dir_relative("md2html")).string(); */
/*     std::replace(tmp1.begin(), tmp1.end(), '/', '\\'); */
/*     //oss << '"' << tmp1 << "\" \""; */
/*     oss << tmp1 << " \""; */
/*     tmp1 = zenox::u8path(mdpath).string(); */
/*     std::replace(tmp1.begin(), tmp1.end(), '/', '\\'); */
/*     oss << tmp1; */
/*     oss << "\" -o \""; */
/*     tmp1 = zenox::u8path(htmlpath).string(); */
/*     std::replace(tmp1.begin(), tmp1.end(), '/', '\\'); */
/*     oss << tmp1 << '"'; */
/* #endif */
/*     zenox::log_critical("now executing: [{}]", oss.str()); */
/*     std::system(oss.str().c_str()); */
    if (!std::filesystem::is_regular_file(htmlpath))
        zenox::fatal_error("failed to convert markdown to html: {}", mdpath);
    std::filesystem::remove(mdpath);
    auto html = zenox::file_get_content(htmlpath);
    std::filesystem::remove(htmlpath);
    return html;
}

static std::string mock_html(std::string const &html, std::string const &path) {
    static std::regex pattern(R"REGEX(<a href="([^"]+).md">)REGEX");
    return R"HTML(<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Zeno 文档页 - )HTML" + path + R"HTML(</title>
<meta name="generator" content="md2html">
<meta name="color-scheme" content="light dark">
<link rel="stylesheet" href="github-markdown.css">
<style>
body {
    box-sizing: border-box;
    min-width: 200px;
    max-width: 980px;
    margin: 0 auto;
    padding: 45px;
}
@media (prefers-color-scheme: dark) {
    body {
        background-color: #0d1117;
    }
}
</style>
</head>
<body>
<article class="markdown-body">
)HTML" + std::regex_replace(html, pattern, R"REGEX(<a href="$1.html">)REGEX")
+ R"HTML(
</article>
</body>
</html>
)HTML";
}

static void build_html_docs() {
    auto htmldir = std::filesystem::temp_directory_path() / zenox::u8path("_zenohtmldocs");
    if (!std::filesystem::exists(htmldir)) {
        std::filesystem::create_directory(htmldir);
        zenox::log_info("building HTML docs, please wait...");
        auto docsdir = zenox::zeno_home_relative("docs");
        zenox::log_trace("translating from MD docs [{}], to HTML docs [{}]", docsdir, zenox::pathu8(htmldir));
        tbb::task_group tg;
        visit_directory_relative(docsdir, [&] (std::string path) {
            tg.run([&htmldir, path = std::move(path)] {
                if (path.ends_with(".md")) {
                    auto fullpath = zenox::zeno_home_relative("docs", path);
                    zenox::log_trace("processing markdown file [{}] [{}]", path, fullpath);
                    auto htmlpath = path.substr(0, path.size() - 3) + ".html";
                    auto fullhtml = zenox::pathu8(htmldir / zenox::u8path(htmlpath));
                    auto html = mock_html(md2html(zenox::file_get_content(fullpath)), path);
                    zenox::file_put_content(fullhtml, html);
                } else if (!path.starts_with(".")) {
                    std::filesystem::copy(zenox::zeno_home_relative("docs", path), htmldir);
                }
            });
        });
        tg.run([&] {
            zenox::log_trace("copying github-markdown-css files: {}/.github-markdown-css", docsdir);
            std::filesystem::copy(zenox::zeno_home_relative("docs/.github-markdown-css/github-markdown-dark.css"), htmldir);
            std::filesystem::copy(zenox::zeno_home_relative("docs/.github-markdown-css/github-markdown-light.css"), htmldir);
            std::filesystem::copy(zenox::zeno_home_relative("docs/.github-markdown-css/github-markdown.css"), htmldir);
        });
        tg.wait();
    } else {
        zenox::log_trace("HTML docs directory already exists in [{}], reusing it now", zenox::pathu8(htmldir));
    }
}

static void shell_execute(std::string page) {
    page = zenox::u8path(page).string();
#ifdef _WIN32
    page = slashstdtowin(page);
    zenox::log_trace("try opening html file via ShellExecuteA : [{}]", page);
    HINSTANCE ret = ShellExecuteA(NULL, NULL, page.c_str(), NULL, NULL, 0);
    if (ret != NULL && ret != INVALID_HANDLE_VALUE) [[likely]] {
        return;
    }
    zenox::log_warn("the html file seems failed to open (exit code {}, file [{}])", (void*)ret, page);
#endif
    std::ostringstream oss;
    const char *chrome_exec =
#ifdef _WIN32
        "explorer";
#define PER_CHROME(x) if (std::system("where " #x) == 0) { chrome_exec = #x; } else
#else
        "thunar";
#define PER_CHROME(x) if (std::system("which " #x " 2> /dev/null") == 0) { chrome_exec = #x; } else
    #endif
    PER_CHROME(chromium)
    PER_CHROME(chrome)
    PER_CHROME(firefox)
    PER_CHROME(gnome-open)
#undef PER_CHROME
    {}
/* #ifdef _WIN32 */
/*     oss << "start "; */
/* #endif */
/*     oss << chrome_exec; */
/*     oss << ' '; */
/* #ifndef _WIN32 */
/*     oss << std::quoted(page, '\'', '\\'); */
/*     oss << " &"; */
/* #else */
/*     oss << page; */
/* #endif */
/*     zenox::log_critical("now executing: [{}]", oss.str()); */
/*     if (int ret = std::system(oss.str().c_str()); ret != 0) [[unlikely]] { */
/*         zenox::log_error("the browser seems failed to start (exit code {}), please open this file manually: {}", ret, page); */
/*     } */
    zenox::subprocess(chrome_exec).argpath(page).detach();
}

static void open_html_doc(std::string page) {
    build_html_docs();
    if (page.ends_with(".md")) {
        page = page.substr(0, page.size() - 3) + ".html";
    }
    auto htmldir = std::filesystem::temp_directory_path() / zenox::u8path("_zenohtmldocs");
    shell_execute(zenox::pathu8(htmldir / page));
}

ZENO_SUBROUTINE(docview)(std::stop_token, std::any) {
    auto htmldir = std::filesystem::temp_directory_path() / zenox::u8path("_zenohtmldocs");
    std::filesystem::remove_all(htmldir);
    open_html_doc("artists.md");
}

ZENO_SUBPROGRAM(splashscreen)(int, char **) {
    std::this_thread::sleep_for(std::chrono::seconds(10));
    return 0;
}

}
